package dump

import (
	"fmt"
	"hash"
	"hash/crc64"
	"io"
	"log"
	"math"
	"os"
	"unsafe"
)

var table = crc64.MakeTable(crc64.ISO)

const MAGIC = "#dump002"
const padding = 0
const SIZEOF_FLOAT32 = 4

// header for dump data frame
type Header struct {
	Magic      string
	Components int
	MeshSize   [3]int
	MeshStep   [3]float64
	MeshUnit   string
	Time       float64
	TimeUnit   string
	DataLabel  string
	DataUnit   string
	Precission uint64
}

func (h *Header) String() string {
	return fmt.Sprintf(
		`     Magic: %v
Components: %v
  MeshSize: %v
  MeshStep: %v
  MeshUnit: %v
      Time: %v
  TimeUnit: %v
 DataLabel: %v
  DataUnit: %v
Precission: %v
`, h.Magic, h.Components, h.MeshSize, h.MeshStep, h.MeshUnit, h.Time, h.TimeUnit, h.DataLabel, h.DataUnit, h.Precission)
}

// Reads successive data frames in dump format.
type Reader struct {
	Header
	HeaderOffset int64 //-1 no offset set
	In           *os.File
	Crc          hash.Hash64
	Err          error
}

func NewReader(in *os.File) *Reader {
	r := new(Reader)
	r.In = in
	r.Crc = crc64.New(table)
	r.HeaderOffset = -1
	return r
}

func (r *Reader) GetHeaderOffset() int64 {
	if r.HeaderOffset < 0 {
		err := r.ReadHeader()
		if err != nil {
			log.Panic(err)
		}
	}
	return r.HeaderOffset
}

func (r *Reader) ReadData() (data []byte, err error) {
	data, err = r.readData()
	// Check CRC
	var mycrc uint64 // checksum by this reader
	if r.Crc != nil {
		mycrc = r.Crc.Sum64()
	}
	storedcrc := r.readUint64() // checksum from data stream. 0 means not set

	if r.Crc != nil {
		r.Crc.Reset() // reset for next frame
	}

	if r.Crc != nil && storedcrc != 0 &&
		mycrc != storedcrc &&
		r.Err == nil {
		r.Err = fmt.Errorf("dump CRC error: expected %16x, got %16x", storedcrc, mycrc)
	}
	return
}

func (r *Reader) ReadAllToBuff(buf []byte) error {
	return r.readAllDataToBuffer(buf)
}

func (r *Reader) ReadFrame() (data []byte, err error) {
	err = r.ReadHeader()
	if err != nil {
		return nil, err
	}
	data, err = r.ReadData()
	return
}

func (r *Reader) readInt() int {
	x := r.readUint64()
	if uint64(int(x)) != x {
		r.Err = fmt.Errorf("value overflows int: %v", x)
	}
	return int(x)
}

// read until the buffer is full
func (r *Reader) read(buf []byte) {
	_, err := io.ReadFull(r.In, buf[:])
	if err != nil {
		r.Err = err
	}
	if r.Crc != nil {
		r.Crc.Write(buf)
	}
}

// read a maximum 8-byte string
func (r *Reader) readString() string {
	var buf [8]byte
	r.read(buf[:])
	// trim trailing NULs.
	i := 0
	for i = 0; i < len(buf); i++ {
		if buf[i] == 0 {
			break
		}
	}
	return string(buf[:i])
}

func (r *Reader) readFloat64() float64 {
	return math.Float64frombits(r.readUint64())
}

func (r *Reader) readUint64() uint64 {
	var buf [8]byte
	r.read(buf[:])
	return *((*uint64)(unsafe.Pointer(&buf[0])))
}

func (r *Reader) ReadHeader() error {
	r.Err = nil // clear previous error, if any
	r.Magic = r.readString()
	if r.Err != nil {
		return r.Err
	}
	if r.Magic != MAGIC {
		r.Err = fmt.Errorf("dump: bad magic number:%v", r.Magic)
		return r.Err
	}
	r.Components = r.readInt()
	for i := range r.MeshSize {
		r.MeshSize[i] = r.readInt()
	}
	for i := range r.MeshStep {
		r.MeshStep[i] = r.readFloat64()
	}
	r.MeshUnit = r.readString()
	r.Time = r.readFloat64()
	r.TimeUnit = r.readString()
	r.DataLabel = r.readString()
	r.DataUnit = r.readString()
	r.Precission = r.readUint64()

	for i := 0; i < padding; i++ {
		_ = r.readUint64()
	}

	if r.Err != nil {
		return r.Err
	}

	r.HeaderOffset, r.Err = r.In.Seek(0, 1)

	return r.Err
}

func (r *Reader) SkipHeader() {
	if r.HeaderOffset < 0 {
		r.GetHeaderOffset()
	}
	_, r.Err = r.In.Seek(r.HeaderOffset, 0)
	return
}

// read the data array,
func (r *Reader) readData() ([]byte, error) {
	s := r.MeshSize
	c := r.Components
	length := c * s[0] * s[1] * s[2]
	buf := make([]byte, SIZEOF_FLOAT32*length)
	r.read(buf)
	if r.Err == nil {
		return buf, nil
	}
	return buf, r.Err
}

// read the data array,
func (r *Reader) readAllDataToBuffer(buf []byte) error {
	r.SkipHeader()
	if r.Err != nil {
		return r.Err
	}
	r.read(buf)
	if r.Err == nil {
		return nil
	}
	return r.Err
}
